/**
 *   _                                                          _   
 *  | | ____ _ _ __ _ __ ___   __ _  ___           _ __ ___ ___| |_ 
 *  | |/ / _` | '__| '_ ` _ \ / _` |/ __|  _____  | '__/ _ | __| __|
 *  |   < (_| | |  | | | | | | (_| | (__  |_____| | | |  __|__ \ |_ 
 *  |_|\_\__,_|_|  |_| |_| |_|\__,_|\___|         |_|  \___|___/\__|
 *         
 *  Copyright  2011 Kondra, Inc. 
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.kondra.karmac.rest
{
	import flash.events.Event;

   /**
    * <code>ISericeTask</code> implementation for executing batch
    * service tasks, either sequentially or in parallel.
    * 
    * @author Jared Kaplan
    */ 
   public class BatchServiceTask extends AbstractServiceTask
   {
      private var m_serviceList:Array;
      private var m_sequence:Boolean;
      private var m_currentTaskIndex:int;
      private var m_resultMap:Object;
      private var m_resultName:String;
      
      public function BatchServiceTask( serviceEndpoint:IServiceEndpoint = null )
      {
         super( serviceEndpoint );
         
         m_serviceList = new Array();
         m_sequence = false;
         m_currentTaskIndex = 0;
         m_resultMap = new Object();
      }
      
      /**
       * Adds an <code>IServiceTask</code> object to this batch request.
       * 
       * @param task          The task that is being added to the batch request
       * @param name          Name that is used to add the service result to 
       *                      result map for the batch request
       */ 
      public function add( task:IServiceTask, name:String = null ) : void 
      {
         // make sure the task hasn't already been added to the 
         // batch service request
         if( getServiceItem( task.getId() ) != null )
            throw new Error( "BatchServiceTask:add() : task has already been added to request!" );
         
         // add the service item to the service list
         m_serviceList.push( new BatchServiceItem( task, name ) ); 
         
         // add notification handler for task completion and error
         task.addNotificationHandler( new ServiceNotificationHandler( handleSingleTaskComplete, handleSingleTaskError ) );
      }
      
      /**
       * Starts the next task scheduled to be executed by this batch
       * service request.
       */ 
      private function startNextService() : void
      {
         var serviceItem:BatchServiceItem = m_serviceList[m_currentTaskIndex++] as BatchServiceItem;
         
         // start the service item task
         serviceItem.start();
      }
      
      /**
       * Returns <code>true</code> if all service tasks have been completed
       * for this batch service request.
       */ 
      private function checkBatchServiceComplete() : Boolean
      {
         var complete:Boolean = true;

         // iterate through the service list and check if each item
         // has been completed
         for each( var next:BatchServiceItem in m_serviceList )
         {
            if( next.isComplete() == false ) {
               complete = false;
               break;
            }
         }
         
         // if all of the service tasks are complete then call the
         // inherited handleRequestComplete() method
         if( complete )
            handleRequestComplete( getResultData(), this );
            
         return complete;
      }
      
      /**
       * Called after a single task in the batch request has completed.
       * 
       * @param result           The <code>ServiceResult</code> object for the task
       *                         that has completed
       */ 
      private function handleSingleTaskComplete( result:ServiceResult ) : void
      {
         var source:IServiceTask = result.source as IServiceTask;
         var serviceItem:BatchServiceItem = source != null ? getServiceItem( source.getId() ) : null;
         
         if( serviceItem != null )
         {
            // set the result data for the associated batch service item object
            serviceItem.setResult( result );
            
            // add the results to the batch query result map
            m_resultMap[serviceItem.name] = result;
         }
            
         // if there are more tasks to execute then start the next task 
         if( checkBatchServiceComplete() == false && m_sequence )
            startNextService();
      }
      
      /**
       * Called when there is an error for a single task. An error in any task
       * will result in the entire batch request failing.
       * 
       * @param result           The <code>ServiceResult</code> object for the task
       *                         that has an error
       */ 
      private function handleSingleTaskError( result:ServiceResult ) : void {
         cancel(); 
         handleRequestError( null, this, result.event, result.errors );
      }
      
      /**
       * Returns the data that is passed to the <code>ServiceResult</code> object
       * when the batch request is complete. If the <code>resultName</code> property
       * has been set then it returns the data from the result map for the corresponding
       * service task, otherwise this method returns the entire result map.
       */ 
      private function getResultData() : Object {
         return m_resultName != null ? m_resultMap[m_resultName] : m_resultMap;
      }

      /**
       * Returns the corresponding <code>BatchServiceItem</code> from the service list,
       * for the task id being specified.
       * 
       * @param id               The task id used to find the corresponding batch item
       */ 
      private function getServiceItem( id:int ) : BatchServiceItem
      {
         // iterate through the service list to find the corresponding
         // batch service item for the specified task id
         for each( var next:BatchServiceItem in m_serviceList )
         {
            if( next.getId() == id )
               return next;   
         }
         
         return null;
      }
      
      //
      // getter/setter methods
      //
      public function set sequence( sequence:Boolean ) : void { m_sequence = sequence; }
      public function get sequence() : Boolean { return m_sequence; }
      
      public function set resultName( resultName:String ) : void { m_resultName = resultName; }
      public function get resultName() : String { return m_resultName; }
      
      //
      // AbstractServiceTask implementation
      //
      
      /**
       * @inheritDoc
       */ 
      protected override function handleInitializeTask() : void {
      }
      
      /**
       * @inheritDoc
       */ 
      protected override function handleInvokeTask() : void
      {
         if( checkBatchServiceComplete() == false )
         {
            if( m_sequence )
               startNextService();
               
            else
            {
               for each( var next:BatchServiceItem in m_serviceList )
                  startNextService();
            }
         }
      }
      
      /**
       * @inheritDoc
       */ 
      protected override function handleCancelTask() : void 
      {
         for each( var next:BatchServiceItem in m_serviceList )
            next.cancel();
      }
	  
	  /**
	   * @inheritDoc
	   */ 
	  protected override function createDefaultServiceResult( data:Object = null, source:Object = null, event:Event = null, errors:Array = null ) : ServiceResult {
		  return new BatchServiceResult( data, source, event, errors );
	  }
   }
}

//
// BatchServiceItem internal class
//

import com.kondra.karmac.rest.IServiceTask;
import com.kondra.karmac.rest.ServiceResult;

internal class BatchServiceItem
{
   private var m_task:IServiceTask;
   private var m_result:ServiceResult;
   private var m_name:String;
   
   public function BatchServiceItem( task:IServiceTask, name:String = null ) : void {
      m_task = task;
      m_name = name;
   }
   
   public function start() : void {
      m_task.start();
   }
   
   public function cancel() : void {
      m_task.cancel();
   }
   
   public function setResult( result:ServiceResult ) : void {
      m_result = result;
   }
   
   public function isComplete() : Boolean {
      return m_result != null;
   }
   
   public function getId() : int {
      return m_task.getId();
   }
   
   //
   // getter/setter methods
   //
   public function get task() : IServiceTask { return m_task; }
   public function get result() : ServiceResult { return m_result; }
   public function get name() : String { return m_name != null ? m_name : String( m_task.getId() ); }
}
